package by.pavlyukevich.paymentsystem.dataaccess.card.impl;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.stereotype.Repository;

import by.pavlyukevich.paymentsystem.dataaccess.card.CardDao;
import by.pavlyukevich.paymentsystem.dataaccess.filter.CardFilter;
import by.pavlyukevich.paymentsystem.dataaccess.filter.Filter;
import by.pavlyukevich.paymentsystem.dataaccess.impl.AbstractDaoImpl;
import by.pavlyukevich.paymentsystem.datamodel.Account_;
import by.pavlyukevich.paymentsystem.datamodel.card.Card;
import by.pavlyukevich.paymentsystem.datamodel.card.Card_;
import by.pavlyukevich.paymentsystem.datamodel.user.UserAccount_;

@Repository
public class CardDaoImpl extends AbstractDaoImpl<Integer, Card> implements CardDao {

	protected CardDaoImpl() {
		super(Card.class);
	}

	@Override
	public List<Card> getList(Filter<Card> cardFilter) {

		CriteriaQuery<Card> criteria = getAllCriteria(cardFilter);

		Root<Card> root = getRoot(criteria, Card.class);

		root.fetch(Card_.account);
		root.fetch(Card_.userAccount).fetch(UserAccount_.individual);

		addCriteriaRestrict(criteria, cardFilter);

		return getList(criteria, cardFilter);
	}

	@Override
	public long getCount(Filter<Card> cardFilter) {
		CriteriaQuery<Long> criteria = getCountAllCriteria();

		// only individuals have cards
		Root<Card> root = getRoot(criteria, Card.class);
		root.join(Card_.userAccount).join(UserAccount_.individual);

		addCriteriaRestrict(criteria, cardFilter);
		return getSingle(criteria);
	}

	private <T> CriteriaQuery<T> addCriteriaRestrict(CriteriaQuery<T> criteria, Filter<Card> filter) {
		if (filter == null) {
			return criteria;
		}
		Root<Card> root = getRoot(criteria, Card.class);
		CriteriaBuilder cBuilder = getEntityManager().getCriteriaBuilder();
		List<Predicate> predicateList = new ArrayList<Predicate>();
		CardFilter cardFilter = (CardFilter) filter;
		if (cardFilter.getAccount() != null) {
			predicateList.add(cBuilder.equal(root.get(Card_.account), cardFilter.getAccount()));
		}

		if (cardFilter.getUserAccount() != null) {
			predicateList.add(cBuilder.equal(root.get(Card_.userAccount),
					cardFilter.getUserAccount()));
		}
		if (cardFilter.getCard() != null) {
			predicateList.add(cBuilder.equal(root.get(Card_.id), cardFilter.getCard().getId()));
		}
		if (cardFilter.getAccountOwner() != null) {
			predicateList.add(cBuilder.or(
					cBuilder.equal(root.join(Card_.account).get(Account_.userAccount),
							cardFilter.getAccountOwner()),
					cBuilder.equal(root.get(Card_.userAccount), cardFilter.getAccountOwner())));
		}

		if (predicateList.size() > 0) {
			Predicate predicateArray[] = predicateList.toArray(new Predicate[0]);
			criteria.where(predicateArray);
		}

		return criteria;
	}
}
